﻿#include "stdafx.h"

#pragma region  启动入口

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
    //初始化窗口，如果成功就进入游戏主循环
    if (!InitializeWindow(hInstance, nCmdShow, width, height, fullScreen))
    {
        MessageBox(0, L"Window Initialization - Failed", L"Error", MB_OK);
        return 0;
    }

    //初始化Direct3D
    if (!InitD3D())
    {
        MessageBox(0, L"Failed to initialize direct3d 12", L"Error", MB_OK);
        Cleanup();
        return 1;
    }

    //游戏主循环
    MainLoop();

    //我们想让GPU在我们释放资源之前执行完所有命令列表
    WaitForPreviousFrame();

    //关闭隔离事件
    CloseHandle(fenceEvent);

    //清理所有东西
    Cleanup();

    return 0;
}

#pragma endregion


#pragma region stdafx函数定义

bool InitializeWindow(HINSTANCE hInstance, int showWnd, int width, int height, bool fullScreen)
{
    //如果是全屏就获取当前屏幕的分辨率并进行赋值
    if (fullScreen)
    {
        HMONITOR homo = MonitorFromWindow(hwnd,MONITOR_DEFAULTTONEAREST);
        MONITORINFO mi = {sizeof(mi)};
        GetMonitorInfo(homo, &mi);

        width = mi.rcMonitor.right - mi.rcMonitor.left;
        height = mi.rcMonitor.bottom - mi.rcMonitor.top;
    }

    //构建用于描述窗口内容的结构体
    WNDCLASSEX wc;

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    //绑定事件回调函数，用于处理事件
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = NULL;
    wc.cbWndExtra = NULL;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL,IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL,IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 2);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = WindowName;
    wc.hIconSm = LoadIcon(NULL,IDI_APPLICATION);

    //注册窗口内容结构体
    if (!RegisterClassEx(&wc))
    {
        MessageBox(NULL, L"Error registering class", L"Error",MB_OK | MB_ICONERROR);
        return false;
    }

    //创建刚刚注册的窗口
    hwnd = CreateWindowEx(NULL, WindowName, WindowTitle,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, width, height,
                          NULL,NULL, hInstance,NULL);

    if (!hwnd)
    {
        MessageBox(NULL, L"Error Createing Window", L"Error",MB_OK | MB_ICONERROR);
        return false;
    }

    //如果想要全屏显示，需要去掉窗口样式，如果需要Debug则推荐使用窗口模式，因为全屏模式下触发断点很麻烦（双显示器大佬可无视）
    if (fullScreen)
    {
        SetWindowLong(hwnd,GWL_STYLE, 0);
    }

    //显示窗口
    ShowWindow(hwnd, showWnd);
    //更新窗口
    UpdateWindow(hwnd);

    return true;
}

void MainLoop()
{
    //接收窗口内所有的用户事件，如果接收到了就进行传送和分发，如果没有事件就运行游戏代码
    MSG msg;
    ZeroMemory(&msg, sizeof(MSG));

    while (Running)
    {
        if (PeekMessage(&msg,NULL, 0, 0,PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
            {
                break;
            }

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else
        {
            //更新游戏逻辑
            Update();
            //执行命令队列
            Render();
        }
    }
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_KEYDOWN:
        if (wParam == VK_ESCAPE)
        {
            if (MessageBox(0, L"Are you sure you want to exit?", L"Really?", MB_YESNO | MB_ICONQUESTION) == IDYES)
            {
                DestroyWindow(hWnd);
            }
        }
        return 0;
    case WM_DESTROY:
        Running = false;
        PostQuitMessage(0);
        return 0;
    default: ;
    }

    return DefWindowProc(hWnd, msg, wParam, lParam);
}

bool InitD3D()
{
    HRESULT hr;
    //-------------------------------------------------创建device--------------------------------------------//

    IDXGIFactory4* dxgiFactory;
    hr = CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory));
    if (FAILED(hr))
    {
        return false;
    }

    //显卡适配器（包括主板上的嵌入显卡）
    IDXGIAdapter1* adapter;

    //我们将会从索引0开始寻找一个合适的可以使用dx12的图形设备
    int adapterIndex = 0;

    //一个显卡被找到了就为true
    bool adapterFound = false;

    //获取第一个支持d3d 12的gpu
    while (dxgiFactory->EnumAdapters1(adapterIndex, &adapter) != DXGI_ERROR_NOT_FOUND)
    {
        DXGI_ADAPTER_DESC1 desc;
        adapter->GetDesc1(&desc);

        if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
        {
            continue;
        }

        //我们想要一个设备，适配direct3d 12（11或更高）
        //第四个参数传递的是nullptr，测试device是否创建成功，但是不会真正的创建device，如果可以创建，将会返回S_FALSE
        hr = D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr);
        if (SUCCEEDED(hr))
        {
            adapterFound = true;
            break;
        }
        adapterIndex++;
    }

    if (!adapterFound)
    {
        return false;
    }

    //正式创建device
    //IID_PPV_ARGS是一个宏，它直接担任了两个参数
    hr = D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0,IID_PPV_ARGS(&device));
    if (FAILED(hr))
    {
        return false;
    }

    //---------------------------------------创建命令队列-------------------------------------------//

    D3D12_COMMAND_QUEUE_DESC cqDesc = {}; //使用默认值
    cqDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
    cqDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
    hr = device->CreateCommandQueue(&cqDesc,IID_PPV_ARGS(&commandQueue));
    if (FAILED(hr))
    {
        return false;
    }

    //----------------------------------------------创建命令分配器-----------------------------------------------//

    for (int i = 0; i < frameBufferCount; i++)
    {
        hr = device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,IID_PPV_ARGS(&commandAlloactor[i]));
        if (FAILED(hr))
        {
            return false;
        }
    }

    //---------------------------------------------创建命令列表------------------------------------------------//

    //命令分配器在GPU正在执行一个与其关联的命令队列时不能直接重置，但是命令列表可以在执行之后直接重置，这也是我们为什么有三个命令分配器，却只有一个命令列表的原因（3缓冲，单线程）
    hr = device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAlloactor[frameIndex],NULL,
                                   IID_PPV_ARGS(&commandList));
    if (FAILED(hr))
    {
        return false;
    }

    //----------------------------------------创建交换链-------------------------------------------//

    DXGI_MODE_DESC backBufferDesc = {}; //描述显示模式
    backBufferDesc.Width = width;
    backBufferDesc.Height = height;
    backBufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; //buffer格式，32位rgba，每个分量8位

    //多采样描述符，因为我们不是多采样，所以这里设置为1（至少为1）
    DXGI_SAMPLE_DESC sampleDes = {};
    sampleDes.Count = 1;

    //描述并创建交换链
    DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
    swapChainDesc.BufferCount = frameBufferCount; //缓冲区数量
    swapChainDesc.BufferDesc = backBufferDesc;
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; //表示管线将会渲染到这个交换链上
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; //在调用present的时候将会丢弃缓冲区数据
    swapChainDesc.OutputWindow = hwnd; //窗口句柄
    swapChainDesc.SampleDesc = sampleDes; //我们的采样描述符
    swapChainDesc.Windowed = !fullScreen; //必须通过调用SetFullScreenState来为全屏设置不封顶的帧率

    IDXGISwapChain* tempSwapChain;

    //命令队列将会在交换链创建的时候刷新一次
    dxgiFactory->CreateSwapChain(commandQueue, &swapChainDesc, &tempSwapChain);

    swapChain = static_cast<IDXGISwapChain3*>(tempSwapChain);

    frameIndex = swapChain->GetCurrentBackBufferIndex();

    //---------------------------------------创建后缓冲区（render target views （rtv））描述符堆--------------------//

    //描述并创建rtv描述符堆
    D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
    rtvHeapDesc.NumDescriptors = frameBufferCount;
    //这个描述符堆类型为rtv描述符堆
    rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;

    //这个描述符堆将不会被shader直接引用（他是shader不可见的）。它会为管线存储输出
    //否则的话，我们就将描述符堆的Flag设置为D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE
    rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;

    hr = device->CreateDescriptorHeap(&rtvHeapDesc,IID_PPV_ARGS(&rtvDescriptorHeap));
    if (FAILED(hr))
    {
        return false;
    }

    //获取描述符堆的内容大小（这是一个rtv描述符堆，里面只应该存放rtv描述符）
    //描述符堆大小可能会根据GPU的不同而不同，这也是为什么我们要询问GPU而不是直接设置大小
    //我们会使用这个打次奥来增加描述符句柄偏移量
    rtvDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);

    //获取描述符堆中第一个描述符句柄，一个句柄是一个指针，但是我们不能像使用C++指针那样使用他
    CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(rtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());

    //为每个缓冲区创建一个RTV（双缓冲区为两个。三缓冲为3个）
    for (int i = 0; i < frameBufferCount; i++)
    {
        //首先从交换链获取第n个缓冲区，然后将它存储在第n个RTV中
        hr = swapChain->GetBuffer(i,IID_PPV_ARGS(&renderTargets[i]));
        if (FAILED(hr))
        {
            return false;
        }

        //创建rtv绑定到响应交换链缓冲区上
        device->CreateRenderTargetView(renderTargets[i], nullptr, rtvHandle);

        //增加偏移量，使其指向下一个rtv句柄
        rtvHandle.Offset(1, rtvDescriptorSize);
    }


    //------------------------------------------创建隔离&隔离事件------------------------------------------------//

    //创建隔离
    for (int i = 0; i < frameBufferCount; i++)
    {
        hr = device->CreateFence(0, D3D12_FENCE_FLAG_NONE,IID_PPV_ARGS(&fence[i]));
        if (FAILED(hr))
        {
            return false;
        }
        fenceValue[i] = 0;
    }

    //创建隔离事件的句柄
    fenceEvent = CreateEvent(nullptr,FALSE,FALSE, nullptr);
    if (fenceEvent == nullptr)
    {
        return false;
    }

    //--------------------------------------------创建根签名---------------------------------------------------//

    //创建一个描述符范围（为descriptor table所用，并且填充他）
    D3D12_DESCRIPTOR_RANGE descriptorTableRanges[1]; //因为目前只有一个constant buffer所以这里就是1
    descriptorTableRanges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV; //设置类型
    descriptorTableRanges[0].NumDescriptors = 1;
    descriptorTableRanges[0].BaseShaderRegister = 0; //在这个descriptoor table中注册的shader的起始索引
    descriptorTableRanges[0].RegisterSpace = 0;
    descriptorTableRanges[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; //添加到根签名描述符表的末尾

    //创建一个描述符表（descriptor table）
    D3D12_ROOT_DESCRIPTOR_TABLE descriptorTable;
    descriptorTable.NumDescriptorRanges = _countof(descriptorTableRanges);
    descriptorTable.pDescriptorRanges = &descriptorTableRanges[0];

    //创建一个根参数，并且填充他
    D3D12_ROOT_PARAMETER rootParameters[1];
    rootParameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; //这个参数是一个描述符表
    rootParameters[0].DescriptorTable = descriptorTable;
    rootParameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX; //当前我们的VS是唯一可以获取这个参数的着色器


    CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;
    rootSignatureDesc.Init(_countof(rootParameters), rootParameters, 0, nullptr,
                           D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |
                           //我们可以拒绝不需要用到它的shader阶段，来获取更佳的性能
                           D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS |
                           D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS |
                           D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS |
                           D3D12_ROOT_SIGNATURE_FLAG_DENY_PIXEL_SHADER_ROOT_ACCESS);

    ID3DBlob* signature;
    hr = D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, nullptr);
    if (FAILED(hr))
    {
        return false;
    }

    hr = device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(),
                                     IID_PPV_ARGS(&rootSignature));
    if (FAILED(hr))
    {
        return false;
    }

    //-------------------------------------------创建顶点和片元着色器--------------------------------------------//

    //当我们想要debug的话，可以在runtime编译shader文件，但是对于发布版，我们可以使用fxc.exe来创建.cso文件，这样可以在运行时获取shader字节码，这比runtime编译shader快得多

    //编译顶点着色器
    ID3DBlob* vertexShader; //d3d blob用于持有顶点着色器编译后字节码
    ID3DBlob* errorBuff; //用于持有任何编译shader时发生的错误信息
    hr = D3DCompileFromFile(L"VertexShader.hlsl", nullptr, nullptr, "main", "vs_5_0",
                            D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, 0, &vertexShader, &errorBuff);
    if (FAILED(hr))
    {
        OutputDebugStringA((char*)errorBuff->GetBufferPointer());
        return false;
    }
    //为顶点着色器填充shader字节码结构体，本质上是一个指向shader字节码和其尺寸的指针
    D3D12_SHADER_BYTECODE vertexShaderBytecode = {};
    vertexShaderBytecode.BytecodeLength = vertexShader->GetBufferSize();
    vertexShaderBytecode.pShaderBytecode = vertexShader->GetBufferPointer();

    //编译片元着色器
    ID3DBlob* pixelShader;
    hr = D3DCompileFromFile(L"PixelShader.hlsl", nullptr, nullptr, "main", "ps_5_0",
                            D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, 0, &pixelShader, &errorBuff);
    if (FAILED(hr))
    {
        OutputDebugStringA((char*)errorBuff->GetBufferPointer());
        return false;
    }

    D3D12_SHADER_BYTECODE pixelShaderBytecode = {};
    pixelShaderBytecode.BytecodeLength = pixelShader->GetBufferSize();
    pixelShaderBytecode.pShaderBytecode = pixelShader->GetBufferPointer();

    //------------------------------------------创建inputLayout-------------------------------------------------//

    //inputLayout被Input Assembler使用，以便他知道如何读取顶点数据
    D3D12_INPUT_ELEMENT_DESC inputLayout[] =
    {
        {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
        //因为POSITION占据了3个32bit，也就是3*（32/8） = 12bytes，所以COLOR的字节偏移就是12
        {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}
    };

    //填充input layout描述符结构
    D3D12_INPUT_LAYOUT_DESC inputLayoutDesc = {};
    inputLayoutDesc.NumElements = sizeof(inputLayout) / sizeof(D3D12_INPUT_ELEMENT_DESC);
    inputLayoutDesc.pInputElementDescs = inputLayout;

    //----------------------------------------创建管线状态对象（PSO）---------------------------------------------//

    //在真正的应用中，对于每个不同的shader，不同的shader组合，不同的混合状态或不同的光栅化状态，不同的拓扑类型（点，线，三角形，四边形），不同数量的render targets。你可能会有很多pso，

    //VS是pso唯一需要的着色器，你可能想知道什么情况下只能设置VS，可能有一个pso仅仅与一个流输出一起输出数据，而不再渲染目标上输出数据，这意味着在流输出之后不需要任何东西

    D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
    psoDesc.InputLayout = inputLayoutDesc;
    psoDesc.pRootSignature = rootSignature;
    psoDesc.VS = vertexShaderBytecode;
    psoDesc.PS = pixelShaderBytecode;
    psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; //绘制的拓扑类型
    psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; //render target格式
    psoDesc.SampleDesc = sampleDes; //采样描述必须和交换链以及深度/模板缓冲区一致
    psoDesc.SampleMask = 0xffffffff; //采样遮罩需要和多重采样一起工作，0xffffffff意味着采样结束
    psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
    psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
    psoDesc.NumRenderTargets = 1;
    psoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT); //默认深度模板缓冲区状态

    hr = device->CreateGraphicsPipelineState(&psoDesc,IID_PPV_ARGS(&pipelineStateObject));
    if (FAILED(hr))
    {
        return false;
    }

    //-------------------------------------------创建顶点缓冲区&&索引缓冲区------------------------------------------------//

    //这是一个矩形顶点
    //可以使用两个三角形来定义，但这样会有顶点重叠，产生性能消耗
    {
        // 重复顶点是完全相同的顶点，包括所有属性，例如颜色，纹理坐标和位置
        // 在后面我们绘制带纹理的立方体时，是不是只需要8个顶点呢？并不是，我们会需要24个顶点，原因是即使8个角的位置在3个不同面之间共享
        // 但该角中每个面的顶点将具有不同的纹理坐标，法线坐标，所以不能移除
        // Vertex vList[] = {
        //     // first triangle
        //     { -0.5f,  0.5f, 0.5f }, // top left
        //     {  0.5f, -0.5f, 0.5f }, // bottom right
        //     { -0.5f, -0.5f, 0.5f }, // bottom left
        //
        //     // second triangle
        //     { -0.5f,  0.5f, 0.5f }, // top left
        //     {  0.5f,  0.5f, 0.5f }, // top right
        //     {  0.5f, -0.5f, 0.5f }  // bottom right
        // };
    }
    //取而代之的是，我们使用Index Buffer，可以使用四个顶点表述一个矩形
    // a quad
    Vertex vList[] = {
        {-0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f},
        {0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f, 1.0f},
        {-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f},
        {0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f}
    };

    int vBufferSize = sizeof(vList);

    //创建default heap
    //default heap是在GPU内存上的，只有GPU有权限访问这块内存，为了获取数据放入这个堆中，我们不得不使用upload heap上传数据
    device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), D3D12_HEAP_FLAG_NONE,
                                    &CD3DX12_RESOURCE_DESC::Buffer(vBufferSize), D3D12_RESOURCE_STATE_COPY_DEST,
                                    nullptr, IID_PPV_ARGS(&vertexBuffer));

    //我们可以赋予resource heaps一个名字，这样我们在使用graphic debugger进行debug的时候就可以知道我们正在查看哪个资源
    vertexBuffer->SetName(L"Vertex Buff Resource heap");

    //创建upload heap，upload heaps被用于上传数据到gpu，cpu可以写入他，gpu可以读取他，我们将利用upload heap上传顶点缓冲区到default heap
    ID3D12Resource* vBufferUploadHeap;
    device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD), D3D12_HEAP_FLAG_NONE,
                                    &CD3DX12_RESOURCE_DESC::Buffer(vBufferSize), D3D12_RESOURCE_STATE_GENERIC_READ,
                                    nullptr,IID_PPV_ARGS(&vBufferUploadHeap));
    vBufferUploadHeap->SetName(L"Vertex Buffer Upload Resource Heap");

    //保存顶点缓冲区都upload heap
    D3D12_SUBRESOURCE_DATA vertexData = {};
    vertexData.pData = reinterpret_cast<BYTE*>(vList); //索引我们的顶点数组
    vertexData.RowPitch = vBufferSize; //三角形顶点数据大小
    vertexData.SlicePitch = vBufferSize; //同上

    //使用命令列表创建一个命令，来从upload heap拷贝数据到default heap
    UpdateSubresources(commandList, vertexBuffer, vBufferUploadHeap, 0, 0, 1, &vertexData);

    //将顶点缓冲区状态从copy destination state转换为vertex buff state
    commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(
                                     vertexBuffer, D3D12_RESOURCE_STATE_COPY_DEST,
                                     D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER));

    // 一个方形索引
    DWORD iList[] = {
        0, 1, 2, // 第一个三角形
        0, 3, 1 // 第二个三角形
    };

    int iBufferSize = sizeof(iList);

    // 创建default heap持有index buffer
    device->CreateCommittedResource(
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), //default heap
        D3D12_HEAP_FLAG_NONE,
        &CD3DX12_RESOURCE_DESC::Buffer(iBufferSize), // 缓冲区的资源描述符
        D3D12_RESOURCE_STATE_COPY_DEST, // 起始为copy destination state
        nullptr, //对于这种类型的资源，优化清理值必须为空
        IID_PPV_ARGS(&indexBuffer));

    indexBuffer->SetName(L"Index Buffer Resource Heap");

    // 创建upload heap来上传index Buffer
    ID3D12Resource* iBufferUploadHeap;
    device->CreateCommittedResource(
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD), // upload heap
        D3D12_HEAP_FLAG_NONE,
        &CD3DX12_RESOURCE_DESC::Buffer(iBufferSize),
        D3D12_RESOURCE_STATE_GENERIC_READ, //GPU将会从这个缓冲区读取数据，并且将其内容拷贝到default heap
        nullptr,
        IID_PPV_ARGS(&iBufferUploadHeap));

    iBufferUploadHeap->SetName(L"Index Buffer Upload Resource Heap");

    // 在upload heap保存索引缓冲区
    D3D12_SUBRESOURCE_DATA indexData = {};
    indexData.pData = reinterpret_cast<BYTE*>(iList);
    indexData.RowPitch = iBufferSize;
    indexData.SlicePitch = iBufferSize;

    //使用命令列表创建一个命令，来从upload heap拷贝数据到default heap
    UpdateSubresources(commandList, indexBuffer, iBufferUploadHeap, 0, 0, 1, &indexData);

    commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(
                                     indexBuffer, D3D12_RESOURCE_STATE_COPY_DEST,
                                     D3D12_RESOURCE_STATE_INDEX_BUFFER));

    //--------------------------------------创建深度/模板缓冲区----------------------------------------//
    D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc = {};
    dsvHeapDesc.NumDescriptors = 1;
    dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
    dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
    hr = device->CreateDescriptorHeap(&dsvHeapDesc,IID_PPV_ARGS(&dsDescriptorHeap));
    if (FAILED(hr))
    {
        Running = false;
    }

    D3D12_DEPTH_STENCIL_VIEW_DESC depthStencilDesc = {};
    depthStencilDesc.Format = DXGI_FORMAT_D32_FLOAT;
    depthStencilDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
    depthStencilDesc.Flags = D3D12_DSV_FLAG_NONE;

    D3D12_CLEAR_VALUE depthOptimizedClearValue = {};
    depthOptimizedClearValue.Format = DXGI_FORMAT_D32_FLOAT;
    depthOptimizedClearValue.DepthStencil.Depth = 1.0f;
    depthOptimizedClearValue.DepthStencil.Stencil = 0;

    device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), D3D12_HEAP_FLAG_NONE,
                                    &CD3DX12_RESOURCE_DESC::Tex2D(DXGI_FORMAT_D32_FLOAT, width, height, 1, 0, 1, 0,
                                                                  D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL),
                                    D3D12_RESOURCE_STATE_DEPTH_WRITE, &depthOptimizedClearValue,
                                    IID_PPV_ARGS(&depthStencilBuffer));
    hr = device->CreateDescriptorHeap(&dsvHeapDesc,IID_PPV_ARGS(&dsDescriptorHeap));
    if (FAILED(hr))
    {
        Running = false;
    }
    dsDescriptorHeap->SetName(L"Depth/Stencil Resource Heap");
    device->CreateDepthStencilView(depthStencilBuffer, &depthStencilDesc,
                                   dsDescriptorHeap->GetCPUDescriptorHandleForHeapStart());

    //------------------------------------为每个帧缓冲区创建constant buffer描述符堆-----------------------------//
    for (int i = 0; i < frameBufferCount; ++i)
    {
        D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {};
        heapDesc.NumDescriptors = 1;
        heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
        heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
        hr = device->CreateDescriptorHeap(&heapDesc,IID_PPV_ARGS(&mainDescriptorHeap[i]));
        if (FAILED(hr))
        {
            Running = false;
        }
    }

    //-----------------------------------创建constant buffer资源堆------------------------------//
    //我们每帧都会更新一次或多次constant buffer，所以我们将会只使用upload heap，
    //而不是我们前面提到的使用一个upload heap上传顶点和索引数据，然后把他们拷贝到defalt heap。
    //如果你打算在多帧使用一个资源，把它拷贝到default heap（位于GPU）会更高效
    //但是在这种情况下，我们constant buffer将会每帧都至少修改和上传一次，所以只使用upload heap

    for (int i = 0; i < frameBufferCount; i++)
    {
        hr = device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD), D3D12_HEAP_FLAG_NONE,
                                             &CD3DX12_RESOURCE_DESC::Buffer(1024 * 64), //这个资源堆大小，由于对齐的要求，必须是64kb的整数倍
                                             D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
                                             IID_PPV_ARGS(&constantBufferUploadHeap[i]));

        constantBufferUploadHeap[i]->SetName(L"Constant Buffer Upload Resource Heap");

        D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
        cbvDesc.BufferLocation = constantBufferUploadHeap[i]->GetGPUVirtualAddress();
        cbvDesc.SizeInBytes = (sizeof(ConstantBuffer) + 255) & ~255;//CB尺寸需要256字节对齐
        device->CreateConstantBufferView(&cbvDesc, mainDescriptorHeap[i]->GetCPUDescriptorHandleForHeapStart());

        ZeroMemory(&cbColorMultiplierData, sizeof(cbColorMultiplierData));

        CD3DX12_RANGE readRange(0, 0);//我们不打算从cpu读取东西
        hr = constantBufferUploadHeap[i]->Map(0, &readRange, reinterpret_cast<void**>(&cbColorMultiplierGPUAddress[i]));
        memcpy(cbColorMultiplierGPUAddress[i], &cbColorMultiplierData, sizeof(cbColorMultiplierData));
    }


    //现在执行命令队列来上传索引数据
    commandList->Close();
    ID3D12CommandList* ppCommandLists[] = {commandList};
    commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

    //增加隔离值，否则这个缓冲区将不会在我们开始绘制的时候上传
    fenceValue[frameIndex]++;
    hr = commandQueue->Signal(fence[frameIndex], fenceValue[frameIndex]);
    if (FAILED(hr))
    {
        Running = false;
    }

    //为三角形创建一个顶点缓冲区视图，我们通过GetGPUVirtualAddress()方法获取顶点指针的内存地址
    vertexBufferView.BufferLocation = vertexBuffer->GetGPUVirtualAddress();
    vertexBufferView.StrideInBytes = sizeof(Vertex);
    vertexBufferView.SizeInBytes = vBufferSize;

    // 创建三角形的索引缓冲区
    indexBufferView.BufferLocation = indexBuffer->GetGPUVirtualAddress();
    indexBufferView.Format = DXGI_FORMAT_R32_UINT; // 32bit的无符号整形（一个双字长，两个字长，一个字两字节 )
    indexBufferView.SizeInBytes = iBufferSize;

    //----------------------------------------填充视窗和裁剪矩形----------------------------------------------------//

    viewport.TopLeftX = 0;
    viewport.TopLeftY = 0;
    viewport.Width = width;
    viewport.Height = height;
    viewport.MinDepth = 0.0f;
    viewport.MaxDepth = 1.0f;

    scissorRect.left = 0;
    scissorRect.top = 0;
    scissorRect.right = width;
    scissorRect.bottom = height;

    return true;
}

void Update()
{
    //更新应用逻辑，例如移动相机，或者计算哪些对象处于视角中
    static float rIncrement = 0.00002f;
    static float gIncrement = 0.00006f;
    static float bIncrement = 0.00009f;

    cbColorMultiplierData.colorMultiplier.x += rIncrement;
    cbColorMultiplierData.colorMultiplier.y += gIncrement;
    cbColorMultiplierData.colorMultiplier.z += bIncrement;

    if (cbColorMultiplierData.colorMultiplier.x >= 1.0 || cbColorMultiplierData.colorMultiplier.x <= 0.0)
    {
        cbColorMultiplierData.colorMultiplier.x = cbColorMultiplierData.colorMultiplier.x >= 1.0 ? 1.0 : 0.0;
        rIncrement = -rIncrement;
    }
    if (cbColorMultiplierData.colorMultiplier.y >= 1.0 || cbColorMultiplierData.colorMultiplier.y <= 0.0)
    {
        cbColorMultiplierData.colorMultiplier.y = cbColorMultiplierData.colorMultiplier.y >= 1.0 ? 1.0 : 0.0;
        gIncrement = -gIncrement;
    }
    if (cbColorMultiplierData.colorMultiplier.z >= 1.0 || cbColorMultiplierData.colorMultiplier.z <= 0.0)
    {
        cbColorMultiplierData.colorMultiplier.z = cbColorMultiplierData.colorMultiplier.z >= 1.0 ? 1.0 : 0.0;
        bIncrement = -bIncrement;
    }

    //把我们constant buffer实例拷贝到对应的constant buffer资源
    memcpy(cbColorMultiplierGPUAddress[frameIndex], &cbColorMultiplierData, sizeof(cbColorMultiplierData));
}

void UpdatePipeline()
{
    //-------------当GPU执行命令队列的时候，这个函数会执行，我们如果我们每帧都想修改render target的话。可以在这里clear color
    HRESULT hr;

    //-------------重置命令分配器和命令列表----------------//
    //我们在重置命令分配器之前，不得不等待GPU完成命令分配器的执行
    WaitForPreviousFrame();

    //我们只能在GPU完成一个命令分配器的时候重置他
    //重置一个命令分配器还得释放存储命令列表的内存
    hr = commandAlloactor[frameIndex]->Reset();
    if (FAILED(hr))
    {
        Running = false;
    }

    //重置命令列表，通过重置这个命令列表，把它转换为“记录中”这一状态，以便我们可以开始记录命令到命令分配器
    //我们引用的这个命令分配器可能有多个命令列表，但是再任何时候只能有一个命令可以被记录。
    //确保其他的命令列表处于关闭状态
    //你会将一个初始PSO传递过来（作为第二个参数），但是在这个教程中，我们只清理rtv，只需要一个初始默认状态的管线，所以我们将第二个参数设置为NULL
    hr = commandList->Reset(commandAlloactor[frameIndex], pipelineStateObject);
    if (FAILED(hr))
    {
        Running = false;
    }

    //开始记录命令到命令列表（命令列表将会存储在commandAllocator中
    //将“frameIndex” render target从当前状态转换为render target状态，这样命令列表将会从这里开始在这个render target上面添加内容
    commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(
                                     renderTargets[frameIndex], D3D12_RESOURCE_STATE_PRESENT,
                                     D3D12_RESOURCE_STATE_RENDER_TARGET));
    //我们再次获取当前rtv句柄，然后可以将它设置为管线输出合并阶段的render target
    CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(rtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), frameIndex,
                                            rtvDescriptorSize);
    //获取深度/模板缓冲区句柄
    CD3DX12_CPU_DESCRIPTOR_HANDLE dsvHandle(dsDescriptorHeap->GetCPUDescriptorHandleForHeapStart());

    //为输出合并阶段设置render target
    commandList->OMSetRenderTargets(1, &rtvHandle,FALSE, &dsvHandle);

    //通过使用ClearRenderTargetView命令来清理render target
    const float clearColor[] = {0.0f, 0.2f, 0.4f, 1.0f};
    commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
    //清理深度/模板缓冲区
    commandList->ClearDepthStencilView(dsDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), D3D12_CLEAR_FLAG_DEPTH,
                                       1.0F, 0, 0, nullptr);

    //绘制三角形
    //设置根签名
    commandList->SetGraphicsRootSignature(rootSignature);

    //设置constant buffer 描述符堆
    ID3D12DescriptorHeap* descriptorHeaps[] = {mainDescriptorHeap[frameIndex]};
    commandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);

    //设置根描述符表的0处为constant buffer描述符堆
    commandList->
        SetGraphicsRootDescriptorTable(0, mainDescriptorHeap[frameIndex]->GetGPUDescriptorHandleForHeapStart());

    commandList->RSSetViewports(1, &viewport);
    commandList->RSSetScissorRects(1, &scissorRect);
    commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    commandList->IASetVertexBuffers(0, 1, &vertexBufferView); //设置顶点缓冲区（通过vertex buffer view引用）
    commandList->IASetIndexBuffer(&indexBufferView); //设置索引缓冲区
    commandList->DrawIndexedInstanced(6, 1, 0, 0, 0);
    commandList->DrawIndexedInstanced(6, 1, 0, 4, 0);

    //将“frameIndex”render target从render target状态设置为当前状态
    //如果debug开启的话，如果render target不处于present状态，你会收到一个警告
    commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(renderTargets[frameIndex],
                                                                          D3D12_RESOURCE_STATE_RENDER_TARGET,
                                                                          D3D12_RESOURCE_STATE_PRESENT));
    hr = commandList->Close();
    if (FAILED(hr))
    {
        Running = false;
    }
}

void Render()
{
    HRESULT hr;

    //更新管线来往命令队列发送命令
    UpdatePipeline();

    //创建一个命令列表数组（这里只有一个命令列表）
    ID3D12CommandList* ppCommandLists[] = {commandList};

    //执行命令列表数组
    commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

    //这个命令添加到命令队列的末尾，我们会知道我们的命令队列何时结束，因为当这个命令队列正在被GPU执行时，这个隔离值将会被设置为来自GPU的“fenceValue”，
    hr = commandQueue->Signal(fence[frameIndex], fenceValue[frameIndex]);
    if (FAILED(hr))
    {
        Running = false;
    }

    //显示当前的后缓冲区
    hr = swapChain->Present(0, 0);
    if (FAILED(hr))
    {
        Running = false;
    }
}

void Cleanup()
{
    //------------释放我们创建的对象，在我们释放任何东西之前，我们希望确保GPU已经完成了任何事情---------//
    //等待GPU完成所有帧
    for (int i = 0; i < frameBufferCount; i++)
    {
        frameIndex = i;
        WaitForPreviousFrame();
    }

    //退出前，获取全屏外的交换链
    BOOL fs = false;
    if (swapChain->GetFullscreenState(&fs,NULL))
    {
        swapChain->SetFullscreenState(false,NULL);
    }

    SAFE_RELEASE(device);
    SAFE_RELEASE(swapChain);
    SAFE_RELEASE(commandQueue);
    SAFE_RELEASE(rtvDescriptorHeap);
    SAFE_RELEASE(commandList);

    for (int i = 0; i < frameBufferCount; ++i)
    {
        SAFE_RELEASE(renderTargets[i]);
        SAFE_RELEASE(commandAlloactor[i]);
        SAFE_RELEASE(fence[i]);

        SAFE_RELEASE(mainDescriptorHeap[i]);
        SAFE_RELEASE(constantBufferUploadHeap[i]);
    }

    SAFE_RELEASE(pipelineStateObject);
    SAFE_RELEASE(rootSignature);
    SAFE_RELEASE(vertexBuffer);
    SAFE_RELEASE(indexBuffer);

    SAFE_RELEASE(depthStencilBuffer);
    SAFE_RELEASE(dsDescriptorHeap);
}

void WaitForPreviousFrame()
{
    HRESULT hr;
    //printf("Presen前----------------：%d\n", frameIndex);
    //交换当前的rtv缓冲区索引，以便在当前缓冲区绘制
    frameIndex = swapChain->GetCurrentBackBufferIndex();
    //printf("Presen后！！！！！！！！！：%d\n", frameIndex);
    //如果当前隔离值仍然小于“fenceValue”，我们知道GPU还没有完成命令队列的调用
    if (fence[frameIndex]->GetCompletedValue() < fenceValue[frameIndex])
    {
        //使用隔离创建一个隔离事件，一旦隔离的当前值为“fenceValue”，他将会被触发
        hr = fence[frameIndex]->SetEventOnCompletion(fenceValue[frameIndex], fenceEvent);
        if (FAILED(hr))
        {
            Running = false;
        }

        //我们会在隔离触发事件前一直等待
        WaitForSingleObject(fenceEvent,INFINITE);
    }

    //为下一帧，增加隔离事件的值
    fenceValue[frameIndex]++;
}

#pragma endregion
